<html><head><meta charset="utf-8"><title>12 彰显细节：看集合源码对我们实际工作的帮助和应用-慕课专栏</title>
			<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1">
			<meta name="renderer" content="webkit">
			<meta property="qc:admins" content="77103107776157736375">
			<meta property="wb:webmaster" content="c4f857219bfae3cb">
			<meta http-equiv="Access-Control-Allow-Origin" content="*">
			<meta http-equiv="Cache-Control" content="no-transform ">
			<meta http-equiv="Cache-Control" content="no-siteapp">
			<link rel="apple-touch-icon" sizes="76x76" href="https://www.imooc.com/static/img/common/touch-icon-ipad.png">
			<link rel="apple-touch-icon" sizes="120x120" href="https://www.imooc.com/static/img/common/touch-icon-iphone-retina.png">
			<link rel="apple-touch-icon" sizes="152x152" href="https://www.imooc.com/static/img/common/touch-icon-ipad-retina.png">
			<link href="https://moco.imooc.com/captcha/style/captcha.min.css" rel="stylesheet">
			<link rel="stylesheet" href="https://www.imooc.com/static/moco/v1.0/dist/css/moco.min.css?t=201907021539" type="text/css">
			<link rel="stylesheet" href="https://www.imooc.com/static/lib/swiper/swiper-3.4.2.min.css?t=201907021539">
			<link rel="stylesheet" href="https://static.mukewang.com/static/css/??base.css,common/common-less.css?t=2.5,column/zhuanlanChapter-less.css?t=2.5,course/inc/course_tipoff-less.css?t=2.5?v=201907051055" type="text/css">
			<link charset="utf-8" rel="stylesheet" href="https://www.imooc.com/static/lib/ueditor/themes/imooc/css/ueditor.css?v=201907021539"><link rel="stylesheet" href="https://www.imooc.com/static/lib/baiduShare/api/css/share_style0_16.css?v=6aba13f0.css"></head>
			<body><div id="main">


<div class="main-con hide-menu">
    <!-- 左侧菜单 & 索引 -->
    
    <div class="right-content" style="padding-left: 0px;">
        <div class="container clearfix" id="top" style="width: 1134px; display: block;">
            
            
            <div class="center_con js-center_con l" style="width: 1134px;">
                <div class="article-con">
                                            <!-- 买过的阅读 -->
                        

                    
                    <div class="art-title" style="margin-top: 0px;">
                        12 彰显细节：看集合源码对我们实际工作的帮助和应用
                    </div>
                    <div class="art-info clearfix">
                        
                        <span class="l">
                            更新时间：2019-09-17 10:19:41
                        </span>
                    </div>
                    <div class="art-top">
                                                <img src="https://img1.mukewang.com/5d8042b200016f3506400359.jpg" alt="">
                                                                        <div class="famous-word-box">
                            <img src="https://www.imooc.com/static/img/column/bg-l.png" alt="" class="bg1 bg">
                            <img src="https://www.imooc.com/static/img/column/bg-r.png" alt="" class="bg2 bg">
                            <div class="famous-word">劳动是一切知识的源泉。<p class="author">——陶铸</p></div>
                        </div>
                                            </div>
                    <div class="art-content js-lookimg">
                        <div id="article_content">
                            <div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">本节中，我们先跳出源码的视角，来看看集合类的类图，看看在设计层面上，是否有可疑借鉴之处，接着通过源码来找找工作中的集合坑，提前扫雷。</p>
</div><div class="cl-preview-section"><h2 id="集合类图" style="font-size: 30px;">1 集合类图</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><img class="" src="https://img.mukewang.com/5d7ef50d00018a8315460536.png" data-original="//img.mukewang.com/5d7ef50d00018a8315460536.png" alt="图片描述">上图是目前我们已学的集合类图，大概可以看出以下几点：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">每个接口做的事情非常明确，比如 Serializable，只负责序列化，Cloneable 只负责拷贝，Map 只负责定义 Map 的接口，整个图看起来虽然接口众多，但职责都很清晰；</li>
<li style="font-size: 20px; line-height: 38px;">复杂功能通过接口的继承来实现，比如 ArrayList 通过实现了 Serializable、Cloneable、RandomAccess、AbstractList、List 等接口，从而拥有了序列化、拷贝、对数组各种操作定义等各种功能；</li>
<li style="font-size: 20px; line-height: 38px;">上述类图只能看见继承的关系，组合的关系还看不出来，比如说 Set 组合封装 Map 的底层能力等。</li>
</ol>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">上述设计的最大好处是，每个接口能力职责单一，众多的接口变成了接口能力的积累，假设我们想再实现一个数据结构类，我们就可以从这些已有的能力接口中，挑选出能满足需求的能力接口，进行一些简单的组装，从而加快开发速度。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">这种思想在平时的工作中也经常被使用，我们会把一些通用的代码块抽象出来，沉淀成代码块池，碰到不同的场景的时候，我们就从代码块池中，把我们需要的代码块提取出来，进行简单的编排和组装，从而实现我们需要的场景功能。</p>
</div><div class="cl-preview-section"><h2 id="集合工作中一些注意事项" style="font-size: 30px;">2 集合工作中一些注意事项</h2>
</div><div class="cl-preview-section"><h3 id="线程安全">2.1 线程安全</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">我们说集合都是非线程安全的，这里说的非线程安全指的是集合类作为共享变量，被多线程读写的时候，才是不安全的，如果要实现线程安全的集合，在类注释中，JDK 统一推荐我们使用 Collections.synchronized* 类， Collections 帮我们实现了 List、Set、Map 对应的线程安全的方法， 如下图：<br>
<img class="" src="https://img.mukewang.com/5d7ef4d400018def13540844.png" data-original="//img.mukewang.com/5d7ef4d400018def13540844.png" alt="图片描述">图中实现了各种集合类型的线程安全的方法，我们以 synchronizedList 为例，从源码上来看下，Collections 是如何实现线程安全的：</p>
</div><div class="cl-preview-section"><pre class="  language-java"><code class="prism  language-java"><span class="token comment">// mutex 就是我们需要锁住的对象</span>
<span class="token keyword">final</span> Object mutex<span class="token punctuation">;</span>  
<span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">SynchronizedList</span><span class="token operator">&lt;</span>E<span class="token operator">&gt;</span>
        <span class="token keyword">extends</span> <span class="token class-name">SynchronizedCollection</span><span class="token operator">&lt;</span>E<span class="token operator">&gt;</span>
        <span class="token keyword">implements</span> <span class="token class-name">List</span><span class="token operator">&lt;</span>E<span class="token operator">&gt;</span> <span class="token punctuation">{</span>
        <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">long</span> serialVersionUID <span class="token operator">=</span> <span class="token operator">-</span>7754090372962971524L<span class="token punctuation">;</span>
        <span class="token comment">// 这个 List 就是我们需要保证线程安全的类</span>
        <span class="token keyword">final</span> List<span class="token operator">&lt;</span>E<span class="token operator">&gt;</span> list<span class="token punctuation">;</span>
        <span class="token function">SynchronizedList</span><span class="token punctuation">(</span>List<span class="token operator">&lt;</span>E<span class="token operator">&gt;</span> list<span class="token punctuation">,</span> Object mutex<span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token keyword">super</span><span class="token punctuation">(</span>list<span class="token punctuation">,</span> mutex<span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token keyword">this</span><span class="token punctuation">.</span>list <span class="token operator">=</span> list<span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
				<span class="token comment">// 我们可以看到，List 的所有操作都使用了 synchronized 关键字，来进行加锁</span>
				<span class="token comment">// synchronized 是一种悲观锁，能够保证同一时刻，只能有一个线程能够获得锁</span>
        <span class="token keyword">public</span> E <span class="token function">get</span><span class="token punctuation">(</span><span class="token keyword">int</span> index<span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>mutex<span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token keyword">return</span> list<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>index<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span>
        <span class="token punctuation">}</span>
        <span class="token keyword">public</span> E <span class="token function">set</span><span class="token punctuation">(</span><span class="token keyword">int</span> index<span class="token punctuation">,</span> E element<span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>mutex<span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token keyword">return</span> list<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>index<span class="token punctuation">,</span> element<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span>
        <span class="token punctuation">}</span>
        <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token keyword">int</span> index<span class="token punctuation">,</span> E element<span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>mutex<span class="token punctuation">)</span> <span class="token punctuation">{</span>list<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>index<span class="token punctuation">,</span> element<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span>
        <span class="token punctuation">}</span>
…………
<span class="token punctuation">}</span>      
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">从源码中我们可以看到 Collections 是通过 synchronized 关键字给 List 操作数组的方法加上锁，来实现线程安全的。</p>
</div><div class="cl-preview-section"><h3 id="集合性能">2.2 集合性能</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">集合的单个操作，一般都没有性能问题，性能问题主要出现的批量操作上。</p>
</div><div class="cl-preview-section"><h4 id="批量新增" style="font-size: 26px;">2.2.1 批量新增</h4>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">在 List 和 Map 大量数据新增的时候，我们不要使用 for 循环 + add/put 方法新增，这样子会有很大的扩容成本，我们应该尽量使用 addAll 和 putAll 方法进行新增，以 ArrayList 为例写了一个 demo 如下，演示了两种方案的性能对比：</p>
</div><div class="cl-preview-section"><pre class="  language-java"><code class="prism  language-java"><span class="token annotation punctuation">@Test</span>
<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testBatchInsert</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
  <span class="token comment">// 准备拷贝数据</span>
  ArrayList<span class="token operator">&lt;</span>Integer<span class="token operator">&gt;</span> list <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token operator">&lt;</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span>i<span class="token operator">&lt;</span><span class="token number">3000000</span><span class="token punctuation">;</span>i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
    list<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token comment">// for 循环 + add</span>
  ArrayList<span class="token operator">&lt;</span>Integer<span class="token operator">&gt;</span> list2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token operator">&lt;</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">long</span> start1 <span class="token operator">=</span> System<span class="token punctuation">.</span><span class="token function">currentTimeMillis</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span>i<span class="token operator">&lt;</span>list<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
    list2<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>list<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  log<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span><span class="token string">"单个 for 循环新增 300 w 个，耗时{}"</span><span class="token punctuation">,</span>System<span class="token punctuation">.</span><span class="token function">currentTimeMillis</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span>start1<span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token comment">// 批量新增</span>
  ArrayList<span class="token operator">&lt;</span>Integer<span class="token operator">&gt;</span> list3 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token operator">&lt;</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">long</span> start2 <span class="token operator">=</span> System<span class="token punctuation">.</span><span class="token function">currentTimeMillis</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  list3<span class="token punctuation">.</span><span class="token function">addAll</span><span class="token punctuation">(</span>list<span class="token punctuation">)</span><span class="token punctuation">;</span>
  log<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span><span class="token string">"批量新增 300 w 个，耗时{}"</span><span class="token punctuation">,</span>System<span class="token punctuation">.</span><span class="token function">currentTimeMillis</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span>start2<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">最后打印出来的日志为：</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">16:52:59.865 [main] INFO demo.one.ArrayListDemo - 单个 for 循环新增 300 w 个，耗时1518<br>
16:52:59.880 [main] INFO demo.one.ArrayListDemo - 批量新增 300 w 个，耗时8</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">可以看到，批量新增方法性能是单个新增方法性能的 189 倍，主要原因在于批量新增，只会扩容一次，大大缩短了运行时间，而单个新增，每次到达扩容阀值时，都会进行扩容，在整个过程中就会不断的扩容，浪费了很多时间，我们来看下批量新增的源码：</p>
</div><div class="cl-preview-section"><pre class="  language-java"><code class="prism  language-java"><span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">addAll</span><span class="token punctuation">(</span>Collection<span class="token operator">&lt;</span><span class="token operator">?</span> <span class="token keyword">extends</span> <span class="token class-name">E</span><span class="token operator">&gt;</span> c<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  Object<span class="token punctuation">[</span><span class="token punctuation">]</span> a <span class="token operator">=</span> c<span class="token punctuation">.</span><span class="token function">toArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">int</span> numNew <span class="token operator">=</span> a<span class="token punctuation">.</span>length<span class="token punctuation">;</span>
  <span class="token comment">// 确保容量充足，整个过程只会扩容一次</span>
  <span class="token function">ensureCapacityInternal</span><span class="token punctuation">(</span>size <span class="token operator">+</span> numNew<span class="token punctuation">)</span><span class="token punctuation">;</span> 
  <span class="token comment">// 进行数组的拷贝</span>
  System<span class="token punctuation">.</span><span class="token function">arraycopy</span><span class="token punctuation">(</span>a<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> elementData<span class="token punctuation">,</span> size<span class="token punctuation">,</span> numNew<span class="token punctuation">)</span><span class="token punctuation">;</span>
  size <span class="token operator">+=</span> numNew<span class="token punctuation">;</span>
  <span class="token keyword">return</span> numNew <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">以上是 ArrayList 批量新增的演示，我们可以看到，整个批量新增的过程中，只扩容了一次，HashMap 的 putAll 方法也是如此，整个新增过程只会扩容一次，大大缩短了批量新增的时间，提高了性能。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">所以如果有人问你当碰到集合批量拷贝，批量新增场景，如何提高新增性能的时候 ，就可以从目标集合初始化方面应答。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">这里也提醒了我们，在容器初始化的时候，最好能给容器赋上初始值，这样可以防止在 put 的过程中不断的扩容，从而缩短时间，上章 HashSet 的源码给我们演示了，给 HashMap 赋初始值的公式为：取括号内两者的最大值（期望的值/0.75+1，默认值 16）。</p>
</div><div class="cl-preview-section"><h4 id="批量删除" style="font-size: 26px;">2.2.2 批量删除</h4>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">批量删除 ArrayList 提供了 removeAll 的方法，HashMap 没有提供批量删除的方法，我们一起来看下 removeAll 的源码实现，是如何提高性能的：</p>
</div><div class="cl-preview-section"><pre class="  language-java"><code class="prism  language-java"><span class="token comment">// 批量删除，removeAll 方法底层调用的是 batchRemove 方法</span>
<span class="token comment">// complement 参数默认是 false,false 的意思是数组中不包含 c 中数据的节点往头移动</span>
<span class="token comment">// true 意思是数组中包含 c 中数据的节点往头移动，这个是根据你要删除数据和原数组大小的比例来决定的</span>
<span class="token comment">// 如果你要删除的数据很多，选择 false 性能更好，当然 removeAll 方法默认就是 false。</span>
<span class="token keyword">private</span> <span class="token keyword">boolean</span> <span class="token function">batchRemove</span><span class="token punctuation">(</span>Collection<span class="token operator">&lt;</span><span class="token operator">?</span><span class="token operator">&gt;</span> c<span class="token punctuation">,</span> <span class="token keyword">boolean</span> complement<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">final</span> Object<span class="token punctuation">[</span><span class="token punctuation">]</span> elementData <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>elementData<span class="token punctuation">;</span>
  <span class="token comment">// r 表示当前循环的位置、w 位置之前都是不需要被删除的数据，w 位置之后都是需要被删除的数据</span>
  <span class="token keyword">int</span> r <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> w <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
  <span class="token keyword">boolean</span> modified <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
  <span class="token keyword">try</span> <span class="token punctuation">{</span>
    <span class="token comment">// 从 0 位置开始判断，当前数组中元素是不是要被删除的元素，不是的话移到数组头</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token punctuation">;</span> r <span class="token operator">&lt;</span> size<span class="token punctuation">;</span> r<span class="token operator">++</span><span class="token punctuation">)</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span>c<span class="token punctuation">.</span><span class="token function">contains</span><span class="token punctuation">(</span>elementData<span class="token punctuation">[</span>r<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">==</span> complement<span class="token punctuation">)</span>
        elementData<span class="token punctuation">[</span>w<span class="token operator">++</span><span class="token punctuation">]</span> <span class="token operator">=</span> elementData<span class="token punctuation">[</span>r<span class="token punctuation">]</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>
    <span class="token comment">// r 和 size 不等，说明在 try 过程中发生了异常，在 r 处断开</span>
    <span class="token comment">// 把 r 位置之后的数组移动到 w 位置之后(r 位置之后的数组数据都是没有判断过的数据，这样不会影响没有判断的数据，判断过的数据可以被删除)</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>r <span class="token operator">!=</span> size<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      System<span class="token punctuation">.</span><span class="token function">arraycopy</span><span class="token punctuation">(</span>elementData<span class="token punctuation">,</span> r<span class="token punctuation">,</span>
                       elementData<span class="token punctuation">,</span> w<span class="token punctuation">,</span>
                       size <span class="token operator">-</span> r<span class="token punctuation">)</span><span class="token punctuation">;</span>
      w <span class="token operator">+=</span> size <span class="token operator">-</span> r<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token comment">// w != size 说明数组中是有数据需要被删除的</span>
    <span class="token comment">// 如果 w、size 相等，说明没有数据需要被删除</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>w <span class="token operator">!=</span> size<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token comment">// w 之后都是需要删除的数据，赋值为空，帮助 gc。</span>
      <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> w<span class="token punctuation">;</span> i <span class="token operator">&lt;</span> size<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>
        elementData<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> null<span class="token punctuation">;</span>
      modCount <span class="token operator">+=</span> size <span class="token operator">-</span> w<span class="token punctuation">;</span>
      size <span class="token operator">=</span> w<span class="token punctuation">;</span>
      modified <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">return</span> modified<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">我们看到 ArrayList 在批量删除时，如果程序执行正常，只有一次 for 循环，如果程序执行异常，才会加一次拷贝，而单个 remove 方法，每次执行的时候都会进行数组的拷贝（当删除的元素正好是数组最后一个元素时除外），当数组越大，需要删除的数据越多时，批量删除的性能会越差，所以在 ArrayList 批量删除时，强烈建议使用 removeAll 方法进行删除。</p>
</div><div class="cl-preview-section"><h3 id="集合的一些坑">2.3 集合的一些坑</h3>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">当集合的元素是自定义类时，自定义类强制实现 equals 和 hashCode 方法，并且两个都要实现。</li>
</ol>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">在集合中，除了 TreeMap 和 TreeSet 是通过比较器比较元素大小外，其余的集合类在判断索引位置和相等时，都会使用到 equals 和 hashCode 方法，这个在之前的源码解析中，我们有说到，所以当集合的元素是自定义类时，我们强烈建议覆写 equals 和 hashCode 方法，我们可以直接使用 IDEA 工具覆写这两个方法，非常方便；</p>
</div><div class="cl-preview-section"><ol start="2">
<li style="font-size: 20px; line-height: 38px;">
<p style="font-size: 20px; line-height: 38px;">所有集合类，在 for 循环进行删除时，如果直接使用集合类的 remove 方法进行删除，都会快速失败，报 ConcurrentModificationException 的错误，所以在任意循环删除的场景下，都建议使用迭代器进行删除；</p>
</li>
<li style="font-size: 20px; line-height: 38px;">
<p style="font-size: 20px; line-height: 38px;">我们把数组转化成集合时，常使用 Arrays.asList(array)，这个方法有两个坑，代码演示坑为：</p>
</li>
</ol>
</div><div class="cl-preview-section"><pre class="  language-java"><code class="prism  language-java"><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testArrayToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
  Integer<span class="token punctuation">[</span><span class="token punctuation">]</span> array <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Integer</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">{</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token number">3</span><span class="token punctuation">,</span><span class="token number">4</span><span class="token punctuation">,</span><span class="token number">5</span><span class="token punctuation">,</span><span class="token number">6</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
  List<span class="token operator">&lt;</span>Integer<span class="token operator">&gt;</span> list <span class="token operator">=</span> Arrays<span class="token punctuation">.</span><span class="token function">asList</span><span class="token punctuation">(</span>array<span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token comment">// 坑1：修改数组的值，会直接影响原 list</span>
  log<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span><span class="token string">"数组被修改之前，集合第一个元素为：{}"</span><span class="token punctuation">,</span>list<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  array<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">;</span>
  log<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span><span class="token string">"数组被修改之前，集合第一个元素为：{}"</span><span class="token punctuation">,</span>list<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token comment">// 坑2：使用 add、remove 等操作 list 的方法时，</span>
  <span class="token comment">// 会报 UnsupportedOperationException 异常</span>
  list<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token number">7</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
坑 <span class="token number">1</span>：数组被修改后，会直接影响到新 List 的值。
坑 <span class="token number">2</span>：不能对新 List 进行 add、remove 等操作，否则运行时会报 UnsupportedOperationException 错误。
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">我们来看下 Arrays.asList 的源码实现，就能知道问题所在了，源码如下图：</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><img class="" src="https://img.mukewang.com/5d7ef4ad0001a58020880820.png" data-original="//img.mukewang.com/5d7ef4ad0001a58020880820.png" alt="图片描述">从上图中，我们可以发现，Arrays.asList 方法返回的 List 并不是 java.util.ArrayList，而是自己内部的一个静态类，该静态类直接持有数组的引用，并且没有实现 add、remove 等方法，这些就是坑 1 和 2 的原因。</p>
</div><div class="cl-preview-section"><ol start="4">
<li style="font-size: 20px; line-height: 38px;">集合 List 转化成数组，我们通常使用 toArray 这个方法，这个方法很危险，稍微不注意，就踩进大坑，我们示例代码如下：</li>
</ol>
</div><div class="cl-preview-section"><pre class="  language-java"><code class="prism  language-java">  <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testListToArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
    List<span class="token operator">&lt;</span>Integer<span class="token operator">&gt;</span> list <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token operator">&lt;</span>Integer<span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token punctuation">{</span>
      <span class="token function">add</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token function">add</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token function">add</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token function">add</span><span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">;</span>

    <span class="token comment">// 下面这行被注释的代码这么写是无法转化成数组的，无参 toArray 返回的是 Object[],</span>
    <span class="token comment">// 无法向下转化成 List&lt;Integer&gt;，编译都无法通过</span>
    <span class="token comment">// List&lt;Integer&gt; list2 = list.toArray();</span>

    <span class="token comment">// 演示有参 toArray 方法，数组大小不够时，得到数组为 null 情况</span>
    Integer<span class="token punctuation">[</span><span class="token punctuation">]</span> array0 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Integer</span><span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    list<span class="token punctuation">.</span><span class="token function">toArray</span><span class="token punctuation">(</span>array0<span class="token punctuation">)</span><span class="token punctuation">;</span>
    log<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span><span class="token string">"toArray 数组大小不够，array0 数组[0] 值是{},数组[1] 值是{},"</span><span class="token punctuation">,</span>array0<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span>array0<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
		
    <span class="token comment">// 演示数组初始化大小正好，正好转化成数组</span>
    Integer<span class="token punctuation">[</span><span class="token punctuation">]</span> array1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Integer</span><span class="token punctuation">[</span>list<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    list<span class="token punctuation">.</span><span class="token function">toArray</span><span class="token punctuation">(</span>array1<span class="token punctuation">)</span><span class="token punctuation">;</span>
    log<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span><span class="token string">"toArray 数组大小正好，array1 数组[3] 值是{}"</span><span class="token punctuation">,</span>array1<span class="token punctuation">[</span><span class="token number">3</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// 演示数组初始化大小大于实际所需大小，也可以转化成数组</span>
    Integer<span class="token punctuation">[</span><span class="token punctuation">]</span> array2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Integer</span><span class="token punctuation">[</span>list<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">+</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    list<span class="token punctuation">.</span><span class="token function">toArray</span><span class="token punctuation">(</span>array2<span class="token punctuation">)</span><span class="token punctuation">;</span>
    log<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span><span class="token string">"toArray 数组大小多了，array2 数组[3] 值是{}，数组[4] 值是{}"</span><span class="token punctuation">,</span>array2<span class="token punctuation">[</span><span class="token number">3</span><span class="token punctuation">]</span><span class="token punctuation">,</span>array2<span class="token punctuation">[</span><span class="token number">4</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token number">19</span><span class="token operator">:</span><span class="token number">33</span><span class="token operator">:</span><span class="token number">07.687</span> <span class="token punctuation">[</span>main<span class="token punctuation">]</span> INFO demo<span class="token punctuation">.</span>one<span class="token punctuation">.</span>ArrayListDemo <span class="token operator">-</span> toArray 数组大小不够，array0 数组<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> 值是null<span class="token punctuation">,</span>数组<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> 值是null<span class="token punctuation">,</span>
<span class="token number">19</span><span class="token operator">:</span><span class="token number">33</span><span class="token operator">:</span><span class="token number">07.697</span> <span class="token punctuation">[</span>main<span class="token punctuation">]</span> INFO demo<span class="token punctuation">.</span>one<span class="token punctuation">.</span>ArrayListDemo <span class="token operator">-</span> toArray 数组大小正好，array1 数组<span class="token punctuation">[</span><span class="token number">3</span><span class="token punctuation">]</span> 值是<span class="token number">4</span>
<span class="token number">19</span><span class="token operator">:</span><span class="token number">33</span><span class="token operator">:</span><span class="token number">07.697</span> <span class="token punctuation">[</span>main<span class="token punctuation">]</span> INFO demo<span class="token punctuation">.</span>one<span class="token punctuation">.</span>ArrayListDemo <span class="token operator">-</span> toArray 数组大小多了，array2 数组<span class="token punctuation">[</span><span class="token number">3</span><span class="token punctuation">]</span> 值是<span class="token number">4</span>，数组<span class="token punctuation">[</span><span class="token number">4</span><span class="token punctuation">]</span> 值是null
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">toArray 的无参方法，无法强转成具体类型，这个编译的时候，就会有提醒，我们一般都会去使用带有参数的 toArray 方法，这时就有一个坑，如果参数数组的大小不够，这时候返回的数组值竟然是空，上述代码中的 array0 的返回值就体现了这点，但我们去看 toArray 源码，发现源码中返回的是 4 个大小值的数据，返回的并不是空，源码如下：</p>
</div><div class="cl-preview-section"><pre class="  language-java"><code class="prism  language-java"><span class="token comment">// List 转化成数组</span>
<span class="token keyword">public</span> <span class="token operator">&lt;</span>T<span class="token operator">&gt;</span> T<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">toArray</span><span class="token punctuation">(</span>T<span class="token punctuation">[</span><span class="token punctuation">]</span> a<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment">// 如果数组长度不够，按照 List 的大小进行拷贝，return 的时候返回的都是正确的数组</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>a<span class="token punctuation">.</span>length <span class="token operator">&lt;</span> size<span class="token punctuation">)</span>
    <span class="token comment">// Make a new array of a's runtime type, but my contents:</span>
    <span class="token keyword">return</span> <span class="token punctuation">(</span>T<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> Arrays<span class="token punctuation">.</span><span class="token function">copyOf</span><span class="token punctuation">(</span>elementData<span class="token punctuation">,</span> size<span class="token punctuation">,</span> a<span class="token punctuation">.</span><span class="token function">getClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  System<span class="token punctuation">.</span><span class="token function">arraycopy</span><span class="token punctuation">(</span>elementData<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> a<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> size<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token comment">// 数组长度大于 List 大小的，赋值为 null</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>a<span class="token punctuation">.</span>length <span class="token operator">&gt;</span> size<span class="token punctuation">)</span>
    a<span class="token punctuation">[</span>size<span class="token punctuation">]</span> <span class="token operator">=</span> null<span class="token punctuation">;</span>
  <span class="token keyword">return</span> a<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">从源码中，我们丝毫看不出为什么 array0 的元素值为什么是 null，最后我们去看方法的注释，发现是这样子描述的：</p>
</div><div class="cl-preview-section"><pre class="  language-java"><code class="prism  language-java">If the list fits in the specified array<span class="token punctuation">,</span> it is returned therein<span class="token punctuation">.</span>  Otherwise<span class="token punctuation">,</span> a <span class="token keyword">new</span> <span class="token class-name">array</span> is
 allocated with the runtime type of the specified array and the size of <span class="token keyword">this</span> list。
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">翻译过来的意思就是说：如果返回的数组大小和申明的数组大小一致，那么就会正常返回，否则，一个新数组就会被分配返回。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">所以我们在使用有参 toArray 方法时，申明的数组大小一定要大于等于 List 的大小，如果小于的话，你会得到一个空数组。</p>
</div><div class="cl-preview-section"><h2 id="总结" style="font-size: 30px;">3 总结</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">本小节，我们详细描述了集合的线程安全、性能优化和日常工作中一些坑，这些问题我们在工作中经常会碰到，稍不留神就会引发线上故障，面试的时候也经常会通过这些问题，来考察大家的工作经验，所以阅读本章时，建议大家自己动手试一试，加深印象。</p>
</div>}
                        </div>
                    </div>
                                            <!-- 买过的阅读 -->
                        <div class="art-next-prev clearfix">
                                                                                                <!-- 已买且开放 或者可以试读 -->
                                    <a href="/read/47/article/853">
                                                                    <div class="prev l clearfix">
                                        <div class="icon l">
                                            <i class="imv2-arrow3_l"></i>
                                        </div>
                                        <p>
                                            11 HashSet、TreeSet 源码解析    
                                        </p>
                                    </div>
                                </a>
                                                                                                                            <!-- 已买且开放 或者可以试读 -->
                                    <a href="/read/47/article/855">
                                                                    <div class="next r clearfix">
                                        <p>
                                            13 差异对比：集合在 Java 7 和 8 有何不同和改进
                                        </p>
                                        <div class="icon r">
                                            <i class="imv2-arrow3_r"></i>
                                        </div>

                                    </div>
                                </a>
                                                    </div>
                                    </div>
                <div class="comments-con js-comments-con" id="coments_con">
                </div>

                
            </div>
            
            
            

        </div>
    </div>
</div>

<div class="modal modal-jiaQun-new hide" id="modal-jiaQun">
    <div class="inner" style="">
        <div class="modal-close js-close-jiaQun">
            <i class="imv2-close"></i>
        </div>
        <div class="content">
            <img src="https://img2.mukewang.com/5d634d40000119e505400602.jpg">
            <div class="right-info">
                <div class="title">
                    扫码加入慕课Java核心用户群
                </div>
                <div class="desc">
                                            <p class="mb6">验证信息：<span id="joincode">1909271435058473</span><span class="copy js-copy-joincode">复制</span></p>
                                        <p class="mb6">QQ讨论群号：906691736</p>
                                            <p>QQ群URL：<a href="https://jq.qq.com/?_wv=1027&amp;k=55RtSbJ" target="_blank">点击访问</a></p>
                                    </div>
            </div>
            <p class="tip">若遇到搜索不到QQ群或加群失败，请联系客服邮箱:kf@imooc.com</p>
        </div>
    </div>
</div>
 
<!-- 专栏介绍页专栏评价 -->

<!-- 专栏介绍页底部三条评价 -->

<!-- 专栏阅读页弹层目录和介绍页页面目录 -->

<!-- 专栏阅读页发布回复 -->

<!-- 专栏阅读页发布评论 -->

<!-- 专栏阅读页底部评论 -->

<!-- 专栏阅读 单个 评论 -->

<!-- 新增回复和展开三条以外回复 -->

<!-- 立即订阅的弹窗 -->












</div></body></html>